use async_graphql::{Context, Error, ErrorExtensions};
use rbatis::rbatis::Rbatis;
use serde::{Deserialize, Serialize};

use crate::features::explorer::{self, modelsgqo::File, modelsgqo::Folder};
use crate::features::quizzes::{self, modelsgqo::Quiz, modelsgqo::QuizItem};
use crate::features::tasks::{self, modelsgqo::Task};
use async_graphql::{Interface, SimpleObject, ID};
use derivative::Derivative;

#[derive(Interface, Clone)]
#[graphql(
  field(name = "id", type = "&ID"),
  field(name = "created_at", type = "&i64"),
  field(name = "updated_at", type = "&i64")
)]
pub enum Node {
  Unknown(Unknown),
  Quiz(Quiz),
  QuizItem(QuizItem),
  Task(Task),
  Folder(Folder),
  File(File),
}

#[non_exhaustive]
pub struct IDPrefix;
impl IDPrefix {
  pub const QUIZ: &'static str = "quiz";
  pub const QUIZ_ITEM: &'static str = "qitm";
  pub const TASK: &'static str = "task";
  pub const USER: &'static str = "user";
  pub const WORDZH: &'static str = "wdzh";
  pub const WORDEN: &'static str = "wden";
  pub const FOLDER: &'static str = "fldr";
  pub const FILE: &'static str = "file";
  pub const FILE_TYPE: &'static str = "fitp";
}

#[derive(async_graphql::SimpleObject, Serialize, Deserialize, Clone)]
pub struct KV {
  pub k: String,
  pub v: String,
}

//#[derive(Derivative)]
//#[derivative(Default)]
#[derive(SimpleObject, Clone)]
pub struct Unknown {
  pub id: ID,
  // local created timestamp, seconds since 1970-01-01
  pub created_at: i64,
  // local updated timestamp, seconds since 1970-01-01
  pub updated_at: i64,
}

/// Similar to Github: GitHub's GraphQL API allows retrieval of a node by its ID, called a "direct node lookup".
pub async fn node(ctx: &Context<'_>, id: &str) -> Option<Node> {
  let rbatis_pool = ctx.data_unchecked::<Rbatis>();

  let mut id_parts = id.split(":");
  let rtype = id_parts.next();
  let rid = id_parts.next();

  match rtype {
    Some(IDPrefix::QUIZ) => {
      println!("got {}:{}", rtype.unwrap(), rid.unwrap());
      let quiz_dbo = quizzes::repository::get_quiz_by_id(rbatis_pool, id).await;
      if quiz_dbo.is_ok() {
        let quiz_gqo: Quiz = quiz_dbo.unwrap().into();
        Some(Node::Quiz(quiz_gqo))
      } else {
        None
      }
    }

    Some(IDPrefix::QUIZ_ITEM) => {
      println!("got {}:{}", rtype.unwrap(), rid.unwrap());
      let quiz_item_dbo = quizzes::repository::get_quiz_item_by_id(rbatis_pool, id).await;
      if quiz_item_dbo.is_ok() {
        let quiz_item_gqo: QuizItem = quiz_item_dbo.unwrap().into();
        Some(Node::QuizItem(quiz_item_gqo))
      } else {
        None
      }
    }

    Some(IDPrefix::TASK) => {
      println!("got {}:{}", rtype.unwrap(), rid.unwrap());
      let result = tasks::repository::find_task_by_id(rbatis_pool, id).await;
      if result.is_ok() {
        let result: Task = result.unwrap().into();
        Some(Node::Task(result))
      } else {
        None
      }
    }

    Some(IDPrefix::FOLDER) => {
      println!("IDPrefix::FOLDER got {}:{}", rtype.unwrap(), rid.unwrap());
      let result = explorer::repository::find_folder_by_id(rbatis_pool, id).await;
      if result.is_ok() {
        let result: Folder = result.unwrap().into();
        Some(Node::Folder(result))
      } else {
        None
      }
    }

    Some(IDPrefix::FILE) => {
      println!("IDPrefix::FILE got {}:{}", rtype.unwrap(), rid.unwrap());
      let result = explorer::repository::find_file_by_id(rbatis_pool, id).await;
      if result.is_ok() {
        let result: File = result.unwrap().into();
        Some(Node::File(result))
      } else {
        None
      }
    }

    _ => {
      println!("unknown type of id = {:?}", id);

      let idd = format!("{}", id);
      Some(Node::Unknown(Unknown {
        id: ID::from(idd),
        created_at: 0,
        updated_at: 0,
      }))
    }
  }
}
